import functools
import gzip
import struct
from typing import Generic, TypeVar, Type

import numpy

from emi.bar.data import BarData, BarList
import io
import numpy as np

from emi.io import write_float_compress, read_float_compress, write_int_compress, read_int_compress
from emi.util import TimeUtils

T = TypeVar('T')

class BarIOAdapter(Generic[T]):

    CUR_VERSION = 0

    def __init__(self,class_type:T):
        self.class_type:Type = class_type
        self.serial_id = None  ## 分配的serai Id

    def to_core_bytes(self,bar:T) -> bytes:
        """
        以CUR_VERSION的版本号序列化
        """
        raise NotImplementedError("未实现")

    def to_detail_bytes(self,bar:T) ->bytes:
        return None

    def load_core_bytes(self,bar:T, core_data: bytes, version:int):
        """
        以version的版本号反序列化
        """
        raise NotImplementedError("未实现")

    def load_detail_bytes(self,bar:T, core_data: bytes, version:int):
        pass


class BarDataIOAdapter(BarIOAdapter[BarData]):

    CUR_VERSION = 0

    def to_core_bytes(self, bar:BarData) -> bytes:
        output = io.BytesIO()
        ##hh_mm_ss = int(bar.datetime.hour * 10000 + bar.datetime.minute * 100 + bar.datetime.second)
        assert bar._extra_bytes is None
        ##压缩存储,保留3个精度。
        write_float_compress(output, [bar.open, bar.high, bar.low, bar.close,bar.pre_close],presicion=3)
        output.write(struct.pack("=qdf",int(bar.volume),bar.amount,bar.price_adjust))
        return output.getvalue()

    def load_core_bytes(self,bar:BarData, core_data: bytes, version:int):
        if version == 0:
            self.load_core_bytes_v0(bar,core_data)
            return
        raise RuntimeError(f"unkonw version : {version}")


    def load_core_bytes_v0(self,bar:BarData, core_data: bytes):
        (bar.open, bar.high, bar.low, bar.close,bar.pre_close),offset = read_float_compress(core_data,0)
        bar.volume,bar.amount,bar.price_adjust = struct.unpack_from("=qdf" ,core_data, offset)


class BarListIOAdapter(BarIOAdapter[BarList]):

    CUR_VERSION = 0

    def to_core_bytes(self,bar:T) ->bytes:
        output = io.BytesIO()
        assert bar._extra_bytes is None
        ##压缩存储,保留3个精度。
        write_float_compress(output, [bar.open, bar.high, bar.low, bar.close, bar.pre_close], presicion=3)
        output.write(struct.pack("=qdf", int(bar.volume), bar.amount, bar.price_adjust))
        return output.getvalue()

    def to_detail_bytes(self,bar_list:BarList) -> bytes:
        output = io.BytesIO()
        bars = bar_list.bars()
        hh_mm_ss_list = np.zeros(len(bars),int)
        volume_list = np.zeros(len(bars),float)
        open_list = np.zeros(len(bars),float)
        high_list = np.zeros(len(bars),float)
        low_list = np.zeros(len(bars),float)
        close_list = np.zeros(len(bars),float)
        amount_list = np.zeros(len(bars),float)

        y_m_d, __h_m_s = TimeUtils.split_datetime(bar_list.datetime)
        pre_h_m_s = __h_m_s
        for i in range(0, len(bars)):
            bar = bars[i]
            the_y_m_d, h_m_s = TimeUtils.split_datetime(bar.datetime)
            ##保证批量存储的数据是同一天和同一维度
            if not the_y_m_d == y_m_d:
                raise RuntimeError("pack_bars_as_model():bar的datetime不同一天")
            if pre_h_m_s > h_m_s:
                raise RuntimeError("pack_bars_as_model():存储的bars要升序排序")
            assert bar._extra_bytes is None  ## 暂不支持extar的存储。
            hh_mm_ss_list[i] = numpy.int16(h_m_s/100)
            volume_list[i] = int(bar.volume)
            open_list[i] = bar.open
            close_list[i] = bar.close
            high_list[i] = bar.high
            low_list[i] = bar.low
            amount_list[i]=bar.amount
            pre_h_m_s = h_m_s

        write_int_compress(output, hh_mm_ss_list)
        write_int_compress(output, volume_list)
        write_float_compress(output, open_list, presicion=3)
        write_float_compress(output, close_list, presicion=3)
        write_float_compress(output, high_list, presicion=3)
        write_float_compress(output, low_list, presicion=3)
        write_float_compress(output, amount_list,presicion=2)

        return gzip.compress(output.getvalue())

    def load_core_bytes(self, target_bar: BarList, core_data: bytes, version: int):
        if version == 0:
            self.__load_core_bytes_v0(target_bar, core_data)
            return
        raise RuntimeError(f"unkonw version : {version}")

    def load_detail_bytes(self, target_bar: BarList, core_data: bytes, version: int):
        if version == 0:
            self.__load_detail_bytes_v0(target_bar, core_data)
            return
        raise RuntimeError(f"unkonw version : {version}")

    def __load_core_bytes_v0(self, bar: BarList, core_data: bytes):
        (bar.open, bar.high, bar.low, bar.close, bar.pre_close), offset = read_float_compress(core_data, 0)
        bar.volume, bar.amount, bar.price_adjust = struct.unpack_from("=qdf", core_data, offset)

    def __load_detail_bytes_v0(self, bar: BarList, origin_core_data: bytes):
        if origin_core_data:
            bar._lazy_load_fun = functools.partial(BarListIOAdapter.layload_barlist_v0,origin_core_data,bar)


    @staticmethod
    def layload_barlist_v0(core_data, bar_list: BarList):
        core_data = gzip.decompress(core_data)
        hh_mm_ss_list,offset = read_int_compress(core_data,0)
        volume_list,offset = read_int_compress(core_data,offset)
        open_list,offset = read_float_compress(core_data,offset)
        close_list,offset = read_float_compress(core_data,offset)
        high_list,offset = read_float_compress(core_data,offset)
        low_list,offset = read_float_compress(core_data,offset)
        amount_list,offset = read_float_compress(core_data,offset)

        bars = []
        today = bar_list.datetime
        pre_close = bar_list.pre_close
        price_adjust = bar_list.price_adjust
        for i in range(0, len(hh_mm_ss_list)):
            hh_mm_ss = hh_mm_ss_list[i] * 100
            hour = int(hh_mm_ss / 10000 + 0.01)
            minute = int((hh_mm_ss / 100) % 100 + 0.01)
            second = int(hh_mm_ss % 100 + 0.01)
            bar = BarData(interval=bar_list.interval,symbol=bar_list.symbol,
                          datetime=today.replace(hour=hour,minute=minute,second=second))
            bar.open = open_list[i]
            bar.close = close_list[i]
            bar.high = high_list[i]
            bar.low = low_list[i]
            bar.volume = volume_list[i]
            bar.amount = amount_list[i]
            bar.price_adjust = price_adjust
            bar.pre_close = pre_close
            bars.append(bar)
        return bars




class BarIOAdapterManager:

    def __init__(self):
        self.register_id_map = {}
        self.register_bar_type_map = {}

    def register_adapter(self,serail_id:int,adapater:BarIOAdapter):
        if self.register_id_map.__contains__(serail_id) :
            raise RuntimeError(f"serial id: {serail_id } 已经注册过")
        if self.register_bar_type_map.__contains__(adapater.class_type):
            raise RuntimeError(f"bar type: {adapater.class_type.__name__ } 已经注册过")
        adapater.serial_id = serail_id
        self.register_id_map[serail_id] = adapater
        self.register_bar_type_map[adapater.class_type] = adapater

    def get_adapter_by_serial_id(self,serail_id:int)->BarIOAdapter:
        adpater = self.register_id_map.get(serail_id)
        if adpater is None:
            raise RuntimeError(f"serial id: {serail_id} 未注册过")
        return adpater

    def get_adapter_by_bar_type(self,bar_type:Type)->BarIOAdapter:
        adpater = self.register_bar_type_map.get(bar_type)
        if adpater is None:
            raise RuntimeError(f"bar type: {bar_type.__name__ } v")
        return adpater

def create_bar_ion_manager() -> BarIOAdapterManager:
    bar_io_manager = BarIOAdapterManager()
    bar_io_manager.register_adapter(1, BarDataIOAdapter(BarData))
    bar_io_manager.register_adapter(2, BarListIOAdapter(BarList))
    return bar_io_manager

bario_adapter_manager = create_bar_ion_manager()

